﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Microsoft.WindowsAPICodePack.Dialogs;
using OfficeOpenXml;
using OfficeOpenXml.Style;
using static ShieldTunnel2.ExcelHelper;
using static ShieldTunnel2.ExpertFormOptions;

namespace ShieldTunnel2
{
	public partial class SpecialRiskEvalPanel : UserControl
	{
		private const string ExpertFormFilePrefix = "专家调查表_";
		private string _expertFormFolderPath = null;
		private IList<Expert> _experts = null;
		private IList<RiskEvent> _riskEvents = null;
		private IList<RiskEvent> _primaryRiskEvent = null;
		private TotalLost _totalLost = null;

		public SpecialRiskEvalPanel()
		{
			InitializeComponent();
		}

		#region Control event handlers

		private void Browser_OnClick(object sender, RoutedEventArgs e)
		{
			var ofd = new CommonOpenFileDialog()
			{
				IsFolderPicker = true
			};
			if (ofd.ShowDialog() != CommonFileDialogResult.Ok)
				return;

			var folderPath = ofd.FileName;
			Debug.Assert(Directory.Exists(folderPath));

			_expertFormFolderPath = folderPath;
			this.ButtonEditExpertFormFolder.Text = _expertFormFolderPath;

			var fileNames = Directory.GetFiles(folderPath)
				.Select(Path.GetFileNameWithoutExtension)
				.Where(fn => fn.StartsWith(ExpertFormFilePrefix))
				.ToList();
			if (fileNames.Count > 0)
			{
				var expertNames = fileNames
					.Select(fn => fn.Substring(ExpertFormFilePrefix.Length))
					.ToList();
				_experts = expertNames
					.Select(en => new Expert() {Name = en})
					.ToArray();
				this.ListBoxEditExpertNames.ItemsSource = expertNames;
			}
			else
			{
				this.ListBoxEditExpertNames.ItemsSource = null;
			}
		}

		private async void ButtonSpecialRiskEval_OnClick(object sender, RoutedEventArgs e)
		{
			if (_experts == null || _experts.Count <= 0)
				return;

			this.TreeListRiskEvents.ItemsSource = null;
			_riskEvents = null;
			_primaryRiskEvent = null;
			_totalLost = null;
			var expertNames = _experts.Select(expert => expert.Name).ToList();
			(_experts, _riskEvents, _primaryRiskEvent, _totalLost) =
				await ReadExpertRiskEvalResultAsync(_expertFormFolderPath, expertNames);

			await CalculateSpecialEvalResultAsync(_experts, _riskEvents, _primaryRiskEvent, _totalLost);

			this.TreeListRiskEvents.ItemsSource = _riskEvents;
		}

		private void ButtonExportEvalResult_OnClick(object sender, RoutedEventArgs e)
		{
			if (_riskEvents == null || _riskEvents.Count <= 0)
				return;

			var ofd = new CommonSaveFileDialog()
			{
				DefaultFileName = "专项风险评估结果",
				DefaultExtension = "xlsx",
			};
			if (ofd.ShowDialog() != CommonFileDialogResult.Ok)
				return;

			var filePath = ofd.FileName;
			var fi = new FileInfo(filePath);
			if (fi.Exists)
			{
				fi.Delete();
				fi = new FileInfo(filePath);
			}

			using (var excelPackage = new ExcelPackage(fi))
			{
				var sheet = excelPackage.Workbook.Worksheets.Add("专项风险评估结果");

				//
				// 表头
				//
				var headers = new[]
				{
					"风险名称", "风险编号", "风险层级", "相对权重",
					"发生概率", "风险损失", "概率等级", "损失等级", "接受准则",
				};
				for (var i = 0; i < headers.Length; i++)
				{
					sheet.Cells[1, i + 1].Value = headers[i];
				}
				sheet.Row(1).Height = 25;
				var range = sheet.Cells[1, 1, 1, headers.Length];
				range.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
				range.Style.VerticalAlignment = ExcelVerticalAlignment.Center;
				range.Style.Border.BorderAround(ExcelBorderStyle.Medium);
				range.Style.Font.Bold = true;

				//
				// 数据内容
				//
				var levelColors = new[]
				{
					System.Drawing.Color.LightGreen,
					System.Drawing.Color.Green,
					System.Drawing.Color.Yellow,
					System.Drawing.Color.Orange,
					System.Drawing.Color.Red,
				};
				for (var i = 0; i < _riskEvents.Count; i++)
				{
					var riskEvent = _riskEvents[i];

					var row = i + 2;
					var col = 1;
					sheet.Cells[row, col++].Value = riskEvent.Name;
					sheet.Cells[row, col++].Value = riskEvent.UidName;
					sheet.Cells[row, col++].Value = riskEvent.Level;
					sheet.Cells[row, col++].Value = riskEvent.FormatedWeight;
					sheet.Cells[row, col++].Value = riskEvent.FormatedPossibility;
					sheet.Cells[row, col++].Value = riskEvent.FormatedTotalLost;
					sheet.Cells[row, col++].Value = riskEvent.FormatedPossibilityLevel;
					sheet.Cells[row, col++].Value = riskEvent.FormatedLostLevel;
					sheet.Cells[row, col].Value = riskEvent.FormatedAcceptanceLevel;

					var acceptanceLevel = riskEvent.AcceptanceLevel;
					Debug.Assert(1 <= acceptanceLevel && acceptanceLevel <= 5);
					var fillColor = levelColors[acceptanceLevel - 1];
					range = sheet.Cells[row, col];
					range.Style.Fill.PatternType = ExcelFillStyle.Solid;
					range.Style.Fill.BackgroundColor.SetColor(fillColor);
					if (acceptanceLevel >= 4)
					{
						range.Style.Font.Bold = true;
					}
				}

				for (var i = 1; i <= headers.Length; i++)
				{
					sheet.Column(i).Width = 10;
				}
				sheet.Column(1).Width = 30;
				sheet.Column(headers.Length).Width = 15;

				range = sheet.Cells[2, 1, _riskEvents.Count + 1, headers.Length];
				range.Style.Border.BorderAround(ExcelBorderStyle.Medium);

				range = sheet.Cells[2, 2, _riskEvents.Count + 1, headers.Length];
				range.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;

				//
				excelPackage.SaveAs(fi);
			}

			MessageBox.Show("专项评估结果输出完毕！");
		}

		private void ButtonExpandTreeListRiskEvents_OnClick(object sender, RoutedEventArgs e)
		{
			var buttonActions = new ValueTuple<Button, Action>[]
			{
				(ButtonExpandTreeListRiskEvents, TreeListRiskEvents.View.ExpandAllNodes),
				(ButtonCollapseTreeListRiskEvents, TreeListRiskEvents.View.CollapseAllNodes),
			};
			Debug.Assert(buttonActions.Any(buttonAction => buttonAction.Item1 == sender));
			buttonActions.First(buttonAction => buttonAction.Item1 == sender).Item2.Invoke();
		}

		#endregion

		#region Read data from expert form

		/// <summary>
		/// 读取“专家个人信息”表
		/// </summary>
		/// <param name="worksheets"></param>
		/// <returns></returns>
		private static IList<int> ReadExpertProfessionalScores(ExcelWorksheets worksheets)
		{
			const string sheetName = "专家个人信息";
			Debug.Assert(worksheets.Any(s => s.Name == sheetName));
			var sheet = worksheets[sheetName];

			var expertScoreItems = new[]
			{
				(Row:2, Col:2, Options:ExpertDepartmentTypes),
				(Row:3, Col:2, Options:ExpertProfessionalTitles),
				(Row:4, Col:2, Options:ExpertAgeSections),
				(Row:5, Col:2, Options:ExpertWorkTimes),
				(Row:6, Col:2, Options:ExpertRiskProfessionalLevels),
				(Row:7, Col:2, Options:ExpertShieldTunnelProfessionalLevels),
			};

			var scores = expertScoreItems.Select(esi =>
				{
					var row = esi.Row;
					var col = esi.Col;
					var options = esi.Options;
					var cellValue = sheet.Cells[row, col].Value.ToString();
					Debug.Assert(options.Any(o => o.Item1 == cellValue));
					return options.First(o => o.Item1 == cellValue).Item2;
				})
				.ToArray();
			return scores;
		}

		/// <summary>
		/// 读取“风险事件重要度”表，构建风险事件的树形结构
		/// </summary>
		/// <param name="worksheets"></param>
		/// <returns></returns>
		private static IList<RiskEvent> ReadRiskEvents(ExcelWorksheets worksheets)
		{
			const string sheetName = "风险事件重要度";
			Debug.Assert(worksheets.Any(s => s.Name == sheetName));
			var sheet = worksheets[sheetName];

			var riskEvents = new List<RiskEvent>();

			//
			// 一级事件
			//
			var nextId = 0;
			var startCol = 1;
			var startRow = 2;
			var level = 1;
			Debug.Assert(IsMergedContentCell(sheet, startRow, startCol));
			var (name, uid) = ParseRiskEventFullName(sheet.Cells[startRow, startCol].Value.ToString());
			var parentEvent = new RiskEvent()
			{
				Name = name,
				Level = level,
				Id = nextId++,
				ParentId = -1,
				Uid = uid,
				CellRow = -1,
				CellCol = -1,
			};
			riskEvents.Add(parentEvent);

			//
			// 二级以上事件
			//
			const int colSkipCount = 4;
			while (IsMergedContentCell(sheet, startRow, startCol))
			{
				do
				{
					(name, uid) = ParseRiskEventFullName(sheet.Cells[startRow, startCol].Value.ToString());
					parentEvent = riskEvents.First(re => re.Level == level && re.Uid == uid);
					Debug.Assert(parentEvent != null && parentEvent.Name == name);

					var childrenEvents = ReadChildrenRiskEvents(sheet, parentEvent, startCol, startRow, ref nextId);
					parentEvent.ChildrenRiskEvents = childrenEvents;
					riskEvents.AddRange(childrenEvents);

					startRow += childrenEvents.Count;
				} while (IsMergedContentCell(sheet, startRow, startCol));

				startCol += colSkipCount;
				startRow = 2;
				level++;
			}

			return riskEvents;
		}

		/// <summary>
		/// 读取Excel表中父风险事件右侧对应的子风险事件
		/// </summary>
		/// <param name="sheet"></param>
		/// <param name="parentEvent"></param>
		/// <param name="startCol"></param>
		/// <param name="startRow"></param>
		/// <param name="startId"></param>
		/// <returns></returns>
		private static IList<RiskEvent> ReadChildrenRiskEvents(ExcelWorksheet sheet,
			RiskEvent parentEvent, int startCol, int startRow, ref int nextId)
		{
			Debug.Assert(IsMergedContentCell(sheet, startRow, startCol));

			Debug.Assert(!IsEmptyCell(sheet.Cells[startRow, startCol + 1]));
			var (name, uid) = ParseRiskEventFullName(sheet.Cells[startRow, startCol + 1].Value.ToString());
			var childrenEvents = new List<RiskEvent>()
			{
				new RiskEvent()
				{
					Name = name,
					Level = parentEvent.Level + 1,
					Id = nextId++,
					ParentId = parentEvent.Id,
					Uid = uid,
					CellRow = startRow,
					CellCol = startCol + 1,
				}
			};

			var row = startRow + 1;
			while (IsMergedEmptyCell(sheet, row, startCol))
			{
				Debug.Assert(!IsEmptyCell(sheet.Cells[row, startCol + 1]));
				(name, uid) = ParseRiskEventFullName(sheet.Cells[row, startCol + 1].Value.ToString());
				var childEvent = new RiskEvent()
				{
					Name = name,
					Level = parentEvent.Level + 1,
					Id = nextId++,
					ParentId = parentEvent.Id,
					Uid = uid,
					CellRow = row,
					CellCol = startCol + 1,
				};
				childrenEvents.Add(childEvent);

				row++;
			}

			return childrenEvents;
		}

		private static (string, int) ParseRiskEventFullName(string eventFullName)
		{
			var pos = eventFullName.LastIndexOf("-U");
			var name = eventFullName.Substring(0, pos);
			var uid = int.Parse(eventFullName.Substring(pos + 2));
			return (name, uid);
		}

		/// <summary>
		/// 读取“风险事件重要度”表，获得各个专家的风险重要度打分
		/// </summary>
		/// <param name="worksheets"></param>
		/// <param name="riskEvents"></param>
		private static void ReadRiskEventImportanceScores(ExcelWorksheets worksheets,
			IList<RiskEvent> riskEvents)
		{
			const string sheetName = "风险事件重要度";
			Debug.Assert(worksheets.Any(s => s.Name == sheetName));
			var sheet = worksheets[sheetName];

			foreach (var riskEvent in riskEvents)
			{
				var row = riskEvent.CellRow;
				var col = riskEvent.CellCol;
				if (row < 1 || col < 1)
					continue; // Skip the root event

				Debug.Assert(!IsEmptyCell(sheet.Cells[row, col])
				             && !IsEmptyCell(sheet.Cells[row, col + 1]));
				var score = ParseCellIntValue(sheet, row, col + 1);
				riskEvent.ImportanceScores.Add(score);
			}
		}

		/// <summary>
		/// 读取“风险事件发生概率”表
		/// </summary>
		/// <param name="worksheets"></param>
		/// <param name="primaryRiskEvents"></param>
		/// <returns></returns>
		private static void ReadRiskEventPossibilityScores(ExcelWorksheets worksheets,
			IList<RiskEvent> primaryRiskEvents)
		{
			const string sheetName = "风险事件发生概率";
			Debug.Assert(worksheets.Any(s => s.Name == sheetName));
			var sheet = worksheets[sheetName];

			var row = 2;
			var col = 1;
			while (!IsEmptyCell(sheet.Cells[row, col]))
			{
				Debug.Assert(!IsEmptyCell(sheet.Cells[row, col + 1]));
				var (name, uid) = ParseRiskEventFullName(sheet.Cells[row, col].Value.ToString());
				var score = ParsePossibilityScore(ParseCellStringValue(sheet, row, col + 1));
				var primaryEvent = primaryRiskEvents.First(re => re.Uid == uid);
				primaryEvent.PossibilityScores.Add(score);

				row++;
			}
		}

		/// <summary>
		/// 读取“风险事件损失”表
		/// </summary>
		/// <param name="worksheets"></param>
		/// <param name="primaryRiskEvents"></param>
		/// <returns></returns>
		private static void ReadRiskEventLostScores(ExcelWorksheets worksheets,
			IList<RiskEvent> primaryRiskEvents)
		{
			const string sheetName = "风险事件损失";
			Debug.Assert(worksheets.Any(s => s.Name == sheetName));
			var sheet = worksheets[sheetName];

			var row = 2;
			var col = 1;
			while (!IsEmptyCell(sheet.Cells[row, col]))
			{
				Debug.Assert(Enumerable.Range(col + 1, 4).All(c => !IsEmptyCell(sheet.Cells[row, c])));

				var (name, uid) = ParseRiskEventFullName(sheet.Cells[row, col].Value.ToString());
				var humanScore = ParseLostScore(ParseCellStringValue(sheet, row, col + 1));
				var economicScore = ParseLostScore(ParseCellStringValue(sheet, row, col + 2));
				var timeScore = ParseLostScore(ParseCellStringValue(sheet, row, col + 3));
				var structureScore = ParseLostScore(ParseCellStringValue(sheet, row, col + 4));

				var primaryEvent = primaryRiskEvents.First(re => re.Uid == uid);
				primaryEvent.HumanLostScores.Add(humanScore);
				primaryEvent.EconomicLostScores.Add(economicScore);
				primaryEvent.TimeLostScores.Add(timeScore);
				primaryEvent.StructureLostScores.Add(structureScore);

				row++;
			}
		}

		/// <summary>
		/// 读取“总损失重要度”表
		/// </summary>
		/// <param name="worksheets"></param>
		/// <param name="totalLost"></param>
		private static void ReadTotalLostImportanceScores(ExcelWorksheets worksheets,
			TotalLost totalLost)
		{
			Debug.Assert(worksheets != null && totalLost != null);
			const string sheetName = "总损失重要度";
			Debug.Assert(worksheets.Any(s => s.Name == sheetName));
			var sheet = worksheets[sheetName];

			totalLost.HumanLostImportanceScores.Add(ParseCellIntValue(sheet, 2, 3));
			totalLost.EconomicLostImportanceScores.Add(ParseCellIntValue(sheet, 3, 3));
			totalLost.TimeLostImportanceScores.Add(ParseCellIntValue(sheet, 4, 3));
			totalLost.StructureLostImportanceScores.Add(ParseCellIntValue(sheet, 5, 3));
		}

		#endregion

		#region Helper methods

		private static async Task<ValueTuple<IList<Expert>, IList<RiskEvent>, IList<RiskEvent>, TotalLost>>
			ReadExpertRiskEvalResultAsync(string expertFormFolderPath, IList<string> expertNames)
		{
			return await Task.Run(() =>
			{
				var experts = new List<Expert>();
				IList<RiskEvent> riskEvents = null;
				IList<RiskEvent> primaryRiskEvent = null;
				TotalLost totalLost = null;

				foreach (var expertName in expertNames)
				{
					var expert = new Expert() {Name = expertName};

					var expertFormFilePath = Path.Combine(expertFormFolderPath,
						$"{ExpertFormFilePrefix}{expertName}.xlsx");
					Debug.Assert(File.Exists(expertFormFilePath));

					var fi = new FileInfo(expertFormFilePath);
					using (var excelPackage = new ExcelPackage(fi))
					{
						var worksheets = excelPackage.Workbook.Worksheets;
						Debug.Assert(worksheets != null && worksheets.Count > 0);

						// 读取专家个人信息
						expert.ProfessionalScores = ReadExpertProfessionalScores(worksheets);

						// 读取风险事件重要度
						if (riskEvents == null)
						{
							riskEvents = ReadRiskEvents(worksheets);
							primaryRiskEvent = riskEvents.Where(re => re.IsPrimaryEvent).ToList();
							totalLost = new TotalLost();
						}
						ReadRiskEventImportanceScores(worksheets, riskEvents);

						// 读取风险事件发生概率
						ReadRiskEventPossibilityScores(worksheets, primaryRiskEvent);

						// 读取风险事件损失
						ReadRiskEventLostScores(worksheets, primaryRiskEvent);

						// 读取总损失重要度
						ReadTotalLostImportanceScores(worksheets, totalLost);
					}

					experts.Add(expert);
				}

				Debug.Assert(experts != null && riskEvents != null
				             && primaryRiskEvent != null && totalLost != null);
				return (experts, riskEvents, primaryRiskEvent, totalLost);
			});
		}

		private async Task CalculateSpecialEvalResultAsync(
			IList<Expert> experts,
			IList<RiskEvent> riskEvents,
			IList<RiskEvent> primaryRiskEvent,
			TotalLost totalLost)
		{
			await Task.Run(() =>
			{
				//
				// 计算专家权重
				//
				Debug.Assert(riskEvents != null
				             && primaryRiskEvent != null
				             && totalLost != null);
				var expertCount = experts.Count;
				var expertWeights = RiskEval.GetExpertWeight(experts);
				Debug.Assert(expertWeights.Count == expertCount);

				//
				// 计算总风险的分项风险权重
				//
				{
					var scores = new[]
					{
						RiskEval.GetScoreBy19ScaleMethod(totalLost.HumanLostImportanceScores),
						RiskEval.GetScoreBy19ScaleMethod(totalLost.EconomicLostImportanceScores),
						RiskEval.GetScoreBy19ScaleMethod(totalLost.TimeLostImportanceScores),
						RiskEval.GetScoreBy19ScaleMethod(totalLost.StructureLostImportanceScores),
					};
					var weights = RiskEval.GetWeightsByImportanceComparison(scores);
					Debug.Assert(weights.Count == 4);
					totalLost.HumanLostWeight = weights[0];
					totalLost.EconomicLostWeight = weights[1];
					totalLost.TimeLostWeight = weights[2];
					totalLost.StructureLostWeight = weights[3];
				}

				//
				// 计算底层风险事件的发生概率与损失概率
				//
				foreach (var riskEvent in primaryRiskEvent)
				{
					var scores = riskEvent.PossibilityScores;
					Debug.Assert(scores.Count == expertCount);
					riskEvent.Possibility = RiskEval.GetFuzzyProbabilityByExpertWeights(expertWeights, scores);

					scores = riskEvent.HumanLostScores;
					Debug.Assert(scores.Count == expertCount);
					riskEvent.HumanLost = RiskEval.GetFuzzyProbabilityByExpertWeights(expertWeights, scores);

					scores = riskEvent.EconomicLostScores;
					Debug.Assert(scores.Count == expertCount);
					riskEvent.EconomicLost = RiskEval.GetFuzzyProbabilityByExpertWeights(expertWeights, scores);

					scores = riskEvent.TimeLostScores;
					Debug.Assert(scores.Count == expertCount);
					riskEvent.TimeLost = RiskEval.GetFuzzyProbabilityByExpertWeights(expertWeights, scores);

					scores = riskEvent.StructureLostScores;
					Debug.Assert(scores.Count == expertCount);
					riskEvent.StructureLost = RiskEval.GetFuzzyProbabilityByExpertWeights(expertWeights, scores);

					riskEvent.TotalLost = riskEvent.HumanLost * totalLost.HumanLostWeight
					                      + riskEvent.EconomicLost * totalLost.EconomicLostWeight
					                      + riskEvent.TimeLost * totalLost.TimeLostWeight
					                      + riskEvent.StructureLost * totalLost.StructureLostWeight;

					riskEvent.PossibilityLevel = RiskEval.GetRiskEvenPosibilityLevel(riskEvent.Possibility);
					riskEvent.LostLevel = RiskEval.GetRiskEvenLostLevel(riskEvent.TotalLost);
					Debug.Assert((1 <= riskEvent.PossibilityLevel && riskEvent.PossibilityLevel <= 5)
					             && (1 <= riskEvent.LostLevel && riskEvent.LostLevel <= 5));
					riskEvent.AcceptanceLevel = RiskEval.GetAcceptanceLevel(riskEvent.PossibilityLevel, riskEvent.LostLevel);
				}

				//
				// 通过重要度计算各个风险事件的子事件的权重
				//
				foreach (var riskEvent in riskEvents)
				{
					if (riskEvent.IsPrimaryEvent)
						continue;

					var childrenEvents = riskEvent.ChildrenRiskEvents;
					Debug.Assert(childrenEvents != null && childrenEvents.Count > 0);

					var scores = new List<double>();
					for (var i = 0; i < childrenEvents.Count; i++)
					{
						scores.Add(RiskEval.GetScoreBy19ScaleMethod(childrenEvents[i].ImportanceScores));
					}

					var weights = RiskEval.GetWeightsByImportanceComparison(scores);

					for (var i = 0; i < childrenEvents.Count; i++)
					{
						childrenEvents[i].Weight = weights[i];
					}
				}

				//
				// 逐层回溯，计算各父事件的发生概率与损失概率
				//
				var maxLevel = riskEvents.Max(re => re.Level);
				for (var i = maxLevel - 1; i >= 1; i--)
				{
					var events = riskEvents.Where(re => re.Level == i && !re.IsPrimaryEvent);
					foreach (var riskEvent in events)
					{
						var childrenEvents = riskEvent.ChildrenRiskEvents;
						Debug.Assert(childrenEvents != null && childrenEvents.Count > 0);

						Debug.Assert(childrenEvents.All(re => re.Possibility >= 0 && re.TotalLost >= 0));
						Debug.Assert(riskEvent.Possibility < 0 && riskEvent.TotalLost < 0);

						riskEvent.Possibility = childrenEvents.Sum(re => re.Possibility * re.Weight);
						riskEvent.TotalLost = childrenEvents.Sum(re => re.TotalLost * re.Weight);

						riskEvent.PossibilityLevel = RiskEval.GetRiskEvenPosibilityLevel(riskEvent.Possibility);
						riskEvent.LostLevel = RiskEval.GetRiskEvenLostLevel(riskEvent.TotalLost);
						Debug.Assert((1 <= riskEvent.PossibilityLevel && riskEvent.PossibilityLevel <= 5)
						             && (1 <= riskEvent.LostLevel && riskEvent.LostLevel <= 5));
						riskEvent.AcceptanceLevel = RiskEval.GetAcceptanceLevel(riskEvent.PossibilityLevel, riskEvent.LostLevel);
					}
				}
			});
		}

		#endregion
	}
}
